using System;
using System.ComponentModel;
using GeneticAlgorithms.Genomes;
using GeneticAlgorithms.Populations;
using GeneticAlgorithms.Operators.Fitness;

namespace GeneticAlgorithms
{
    /// <summary>
    /// Base, general-usage class for genetic algorithms.
    /// </summary>
    /// <typeparam name="TGenome">The type of the genome.</typeparam>
    /// <typeparam name="TGene">The type of the genes that will compose each genome in the population.</typeparam>
    public class GeneticAlgorithm<TGenome, TGene> : IGeneticAlgorithm<TGenome, TGene>
        where TGenome : IGenome<TGene>
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="GeneticAlgorithm&lt;TGenome, TGene&gt;"/> class.
        /// </summary>
        /// <param name="population">The population.</param>
        public GeneticAlgorithm(
            IPopulation<TGenome, TGene> population
            )
            : this(population, GAConfigurationBase.Default)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GeneticAlgorithm&lt;TGenome, TGene&gt;"/> class.
        /// </summary>
        /// <param name="population">The population this GA will manipulate.</param>
        /// <param name="configuration">The GA configuration.</param>
        public GeneticAlgorithm(
            IPopulation<TGenome, TGene> population,
            IGAConfiguration configuration
            )
        {
            Population = population;
            Configuration = configuration;
            Status = new GAStatus();
            Initialize();
        }

        #endregion


        #region Methods
        #endregion

        #region IGeneticAlgorithm<TGene> Members

        /// <summary>
        /// Gets the best genome in the population.
        /// </summary>
        /// <returns>
        /// The fittest genome in the population.
        /// </returns>
        public IGenomeBase GetBest()
        {
            return Population.GetBest();
        }

        /// <summary>
        /// Gets the population.
        /// </summary>
        /// <value>The population.</value>
        public IPopulation<TGenome, TGene> Population
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets the GA configuration.
        /// </summary>
        /// <value>The configuration that dictates the GA behavior.</value>
        public IGAConfiguration Configuration
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the status.
        /// </summary>
        /// <value>The status.</value>
        public IGAStatus Status
        {
            get;
            private set;
        }

        /// <summary>
        /// Initializes the Genetic Algorithm.
        /// </summary>
        public void Initialize()
        {
            Population.Initialize();
            UpdateStatus();
            Status.RunStatus = GARunStatus.Initialized;
        }

        /// <summary>
        /// Performs a single evolutionary step.
        /// </summary>
        public void DoStep()
        {
            ExecuteStep();
            Status.RunStatus = GARunStatus.Stepped;
        }

        private bool ExecuteStep()
        {
            Population.DoStep();
            UpdateStatus();
            return DoStepped();
        }

        DateTime? startTime;

        /// <summary>
        /// Evolves the genomes until one of the
        /// stop conditions has been met.
        /// </summary>
        public void Evolve()
        {
            bool cancelled = false;
            startTime = DateTime.Now;
            Status.RunStatus = GARunStatus.Running;
            while (!CheckStop()) 
            {
                if (!ExecuteStep())
                {
                    cancelled = true;
                    break;
                }
            }
            Status.RunStatus = GARunStatus.Stopped;
            if (!cancelled)
            {
                DoStepped();
            }
            startTime = null;
        }

        /// <summary>
        /// Updates the GA status.
        /// </summary>
        protected virtual void UpdateStatus()
        {
            Population.UpdateStats();
            Status.Generation = Population.Generations;
            Status.AverageFitness = Population.AverageFitness.Value;
            Status.BestFitness = Population.BestFitness;
            Status.Variance = Population.Variance.Value;
            Status.PopSize = Population.Count;
            Status.GenerationVariance = Population.GenerationVariance;
            if (startTime.HasValue)
            {
                Status.ElapsedTime = DateTime.Now - startTime.Value;
            }
        }

        /// <summary>
        /// Checks whether the evolution stop.
        /// </summary>
        /// <returns></returns>
        protected virtual bool CheckStop()
        {
            if (Configuration.DesiredFitness <= Status.BestFitness)
            {
                return true;
            }

            if (Configuration.MaxGenerations <= Status.Generation)
            {
                return true;
            }

            if (Configuration.MaxMilliseconds.HasValue && (startTime != null))
            {
                TimeSpan interval = DateTime.Now - startTime.Value;
                if (interval.TotalMilliseconds >= Configuration.MaxMilliseconds.Value)
                {
                    return true;
                }
            }

            if ((Configuration.MinGenerationVariance.HasValue)
                && (Status.GenerationVariance.HasValue)
                && (Status.GenerationVariance.Value < Configuration.MinGenerationVariance))
            {
                return true;
            }

            return false;
        }

        private bool DoStepped()
        {
            if (Stepped != null)
            {
                CancelEventArgs args = new CancelEventArgs(false);
                Stepped(this, args);
                return ! args.Cancel;
            }

            return true;
        }

        /// <summary>
        /// Occurs after each evolutionary step.
        /// </summary>
        public event CancelEventHandler Stepped;

        #endregion
    }

    /// <summary>
    /// Represents a canonic Genetic Algorithm.
    /// </summary>
    public class GeneticAlgorithm : GeneticAlgorithm<BinaryGenome, bool>
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="GeneticAlgorithm&lt;TGenome, TGene&gt;"/> class.
        /// </summary>
        /// <param name="populationSize">Size of the population.</param>
        /// <param name="genomeLength">Length of the genome.</param>
        /// <param name="calculateFitness">The function that calculates fitness.</param>
        public GeneticAlgorithm(
            int populationSize,
            int genomeLength,
            Func<BinaryGenome, double> calculateFitness)
            : this(
            populationSize,
            genomeLength,
            new FitnessLambdaWrapper<BinaryGenome, bool>(calculateFitness))
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GeneticAlgorithm&lt;TGenome, TGene&gt;"/> class.
        /// </summary>
        /// <param name="populationSize">Size of the population.</param>
        /// <param name="genomeLength">Length of the genome.</param>
        /// <param name="fitnessProvider">The fitness provider.</param>
        public GeneticAlgorithm(
            int populationSize,
            int genomeLength,
            IFitnessProvider<BinaryGenome, bool> fitnessProvider)
            : this(
            populationSize,
            genomeLength,
            fitnessProvider,
            GAConfigurationBase.Default)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GeneticAlgorithm&lt;TGenome, TGene&gt;"/> class.
        /// </summary>
        /// <param name="populationSize">Size of the population.</param>
        /// <param name="genomeLength">Length of the genome.</param>
        /// <param name="calculateFitness">The function that calculates fitness.</param>
        /// <param name="configuration">The GA configuration.</param>
        public GeneticAlgorithm(
            int populationSize,
            int genomeLength,
            Func<BinaryGenome, double> calculateFitness,
            IGAConfiguration configuration)
            : this(
            populationSize,
            genomeLength,
            new FitnessLambdaWrapper<BinaryGenome, bool>(calculateFitness),
            configuration)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GeneticAlgorithm&lt;TGenome, TGene&gt;"/> class.
        /// </summary>
        /// <param name="populationSize">Size of the population.</param>
        /// <param name="genomeLength">Length of the genome.</param>
        /// <param name="fitnessProvider">The fitness provider.</param>
        /// <param name="configuration">The GA configuration.</param>
        public GeneticAlgorithm(
            int populationSize,
            int genomeLength,
            IFitnessProvider<BinaryGenome, bool> fitnessProvider,
            IGAConfiguration configuration)
            : base(new BinaryPopulation(populationSize, genomeLength, fitnessProvider), configuration)
        {
        }
    }
}
